#include "helper.h"
#include "monitor.h"
#include "reg.h"

extern uint32_t instr;
extern char assembly[80];

/* decode R-type instrucion */
static void decode_r_type(uint32_t instr) {

	//rs
	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);
	

	//rt
	op_src2->type = OP_TYPE_REG;
	op_src2->imm = (instr & RT_MASK) >> (RD_SIZE + SHAMT_SIZE + FUNC_SIZE);
	op_src2->val = reg_w(op_src2->reg);


	//rd
	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RD_MASK) >> (SHAMT_SIZE + FUNC_SIZE);
	op_dest->val = reg_w(op_dest->reg);


	//sa is missing
}

static uint32_t get_SA_from_instr(uint32_t instr){
	return (instr & SHAMT_MASK) >> (FUNC_SIZE);
}

static int64_t sign_extend_64(int32_t instr_imm){
	long long int result = instr_imm;
	if(result >> 31 == 1 ){
		result |= 0xffffffff00000000;
	}
	return result;
}


make_helper(and) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val & op_src2->val);
	sprintf(assembly, "and   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(or){
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "xor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

}

make_helper(xor){
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val ^ op_src2->val;
	sprintf(assembly, "or   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

}

make_helper(nor){
	decode_r_type(instr);

	reg_w(op_dest->reg) = !(op_src1->val | op_src2->val);

	sprintf(assembly, "nor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sra){
	decode_r_type(instr);

	uint32_t imm = get_SA_from_instr(instr) ; 

	reg_w(op_dest->reg) = (int)(op_src2->val) >> imm;
	sprintf(assembly, "sra   %s,   %s,   %04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), get_SA_from_instr(instr));
}

make_helper(srav){
	decode_r_type(instr);
	reg_w(op_dest->reg) = (int)op_src2->val >> op_src1->val;
	sprintf(assembly, "srav   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(srl){
	decode_r_type(instr);

	uint32_t sa = get_SA_from_instr(instr);

	if(sa == 0){
		reg_w(op_dest->reg) = (op_src2->val);
	}else{
		reg_w(op_dest->reg) = (op_src2->val) >> sa;
	}


	sprintf(assembly, "srl   %s,   %s,   %04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), sa);
}

make_helper(srlv){
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src2->val >> op_src1->val;
	sprintf(assembly, "srlv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sll) {

	decode_r_type(instr);
	
	uint32_t imm = get_SA_from_instr(instr);

	reg_w(op_dest->reg) = op_src2->val << imm;
	
	sprintf(assembly, "sll   %s,   %s,   %04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), imm);

}

make_helper(sllv) {

	decode_r_type(instr);

	reg_w(op_dest->reg) = op_src2->val << op_src1->val;
	
	sprintf(assembly, "sllv   %s,   %s,   %04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), (int)op_src1->val);

}

make_helper(mult){

	int64_t op_1 = sign_extend_64(op_src1->val);

	int64_t op_2 = sign_extend_64(op_src2->val);

	int64_t temp = op_1 * op_2;

	cpu.lo = temp & 0xffffffff;


	cpu.hi = (temp >> 32) & 0xffffffff;

	sprintf(assembly, "mult   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

}

make_helper(multu){
	decode_r_type(instr);

	uint64_t tmp = op_src1->val * op_src2->val;

	cpu.hi = (tmp >> 32) & 0xffffffff;

	cpu.lo = tmp & 0xffffffff;

	//operation & is aiming at no error

	sprintf(assembly, "multu   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(addu){
	decode_r_type(instr);

	reg_w(op_dest->reg) = (op_src2->val) + (op_src1->val);

	sprintf(assembly, "addu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(div){
	decode_r_type(instr);

	cpu.lo = (int)(op_src1->val) / (int)(op_src2->val);
	cpu.hi = (int)(op_src1->val) % (int)(op_src2->val);

	sprintf(assembly, "div   %s,   %s  ->  lo , hi", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	
}

make_helper(divu){
	decode_r_type(instr);

	cpu.lo = op_src1->val / op_src2->val;
	cpu.hi = op_src1->val % op_src2->val;
	//no sign 

	sprintf(assembly, "divu   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

}

make_helper(mfhi){
	decode_r_type(instr);

	reg_w(op_dest->reg) = cpu.hi;

	sprintf(assembly, "mfhi  hi -> %s", REG_NAME(op_dest->reg));
}

make_helper(mflo){
	decode_r_type(instr);

	reg_w(op_dest->reg) = cpu.lo;

	sprintf(assembly, "mflo  lo -> %s", REG_NAME(op_dest->reg));
}

make_helper(slt){
	decode_r_type(instr);

	if((int)op_src1->val < (int)op_src2->val){
		//signed need a cast
		reg_w(op_dest->reg) = 1;
	}
	else{
		reg_w(op_dest->reg) = 0;
	}

	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(sltu){
	decode_r_type(instr);

	if(op_src1->val < op_src2->val){
		// no signed no need cast
		reg_w(op_dest->reg) = 1;
	}
	else{
		reg_w(op_dest->reg) = 0;
	}

	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));


}

make_helper(sub){
	decode_r_type(instr);

	uint32_t temp = (op_src1->val) - (op_src2->val);
	uint32_t val_d = (~op_src2->val) + 1;

	if( (int)(temp ^ op_src1->val) <0 && (int)(temp ^ val_d ) < 0 ){
		exception( Ov , 0 );
		//overflow
	}else{
		reg_w(op_dest->reg) = temp;
	}

	sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(subu){
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val - op_src2->val;
	//unsigned 
	sprintf(assembly, "subu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
}

make_helper(add){
	decode_r_type(instr);

	uint32_t temp = (op_src1->val) + (op_src2->val);

	if(((int)(temp ^ op_src1->val) < 0) && ((int)(temp ^ op_src2->val) < 0)){
		assert(0);
	}
	else{
		reg_w(op_dest->reg) = temp;
	}

	sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

	sprintf(assembly ,"result is %08x" , reg_w(op_dest->reg) );
}

make_helper(jr){
	decode_r_type(instr);
	cpu.pc = op_src1->val;
	sprintf(assembly, "jr  %s", REG_NAME(op_dest->reg));
	
}

make_helper(jalr){
	decode_r_type(instr);

	reg_w(op_dest->reg) = cpu.pc + 8;

	cpu.pc = op_src1->val;

	sprintf(assembly, "jalr  %s , %s", REG_NAME(op_dest->reg),REG_NAME(op_src1->reg));

}